﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ADDDF.Infrastructure.Collections
{
    public class EntityValueSet<T> : IEntitySet<T>, IEntitySet, IEntityValueSet<T>, IEntityValueSet
        where T: IEntity
    {
        private List<T> _entities;
        private Func<T, T> _cloneFunc;

        #region Events

        protected event EventHandler<CloneEntityEventArgs> CloneEntityEvent;
        protected event EventHandler<EntitySetOperationEventArgs> BeforeEntitySetOperationEvent;

        #endregion

        #region Constructors

        public EntityValueSet()
            : this(null, null)
        {            
        }

        public EntityValueSet(IEnumerable<T> entities)
            : this(entities, null)
        {            
        }

        public EntityValueSet(IEnumerable<T> entities, Func<T, T> cloneFunc)
        {
            _cloneFunc = cloneFunc;

            if (entities == null)
                _entities = new List<T>();
            else 
                _entities = new List<T>(entities);
        }

        #endregion
        
        #region IEntitySet<T> implementation

        public T FindByKey(object key)
        {
            T found = FindReferenceByKey(key);

            if (found == null)
                return default(T);
            else
                return CloneEntity(found);
        }

        public IEnumerable<T> GetAll()
        {
            List<T> result = new List<T>();

            foreach (T entity in _entities)
                result.Add(CloneEntity(entity));
            
            return result;
        }

        public bool AddAndReplace(T entity)
        {
            if (BeforeEntitySetOperationEvent != null)
            {
                AddAndReplaceEntityEventArgs eventArgs = new AddAndReplaceEntityEventArgs(entity);

                BeforeEntitySetOperationEvent.Invoke(this, eventArgs);

                if (eventArgs.Cancel)
                    throw new EntitySetOperationCanceledException("AddAndReplace operation was canceled");
            }

            bool removed = Remove(entity);

            _entities.Add(CloneEntity(entity));

            return removed;
        }

        public bool Remove(T entity)
        {
            if (BeforeEntitySetOperationEvent != null)
            {
                RemoveEntityEventArgs eventArgs = new RemoveEntityEventArgs(entity);

                BeforeEntitySetOperationEvent.Invoke(this, eventArgs);

                if (eventArgs.Cancel)
                    throw new EntitySetOperationCanceledException("Remove operation was canceled");
            }

            T found = FindReferenceByKey(entity.Key);

            if (found == null)
                return false;

            _entities.Remove(found);

            return true;
        }

        public void RemoveAll()
        {
            if (BeforeEntitySetOperationEvent != null)
            {
                RemoveAllEntitiesEventArgs eventArgs = new RemoveAllEntitiesEventArgs();

                BeforeEntitySetOperationEvent.Invoke(this, eventArgs);

                if (eventArgs.Cancel)
                    throw new EntitySetOperationCanceledException("RemoveAll operation was canceled");
            }

            _entities.Clear();
        }

        public int Count()
        {
            return _entities.Count();
        }

        public IEnumerable<T> Subtract(IEntitySet<T> another)
        {
            List<T> result = new List<T>();

            foreach (T entity in _entities)
                if (another.FindByKey(entity) == null)
                    result.Add(CloneEntity(entity));

            return result;
        }

        public IEnumerable<T> Intersect(IEntitySet<T> another)
        {
            List<T> result = new List<T>();

            foreach (T entity in _entities)
                if (another.FindByKey(entity) != null)
                    result.Add(CloneEntity(entity));

            return result;
        }

        #endregion
        
        #region Private Members

        private T FindReferenceByKey(object key)
        {
            foreach (T entity in _entities)
            {
                if (KeyEquals(entity.Key, key))
                    return entity;
            }

            return default(T);
        }

        //private void AssignNewKeyIfKeyIsAssignable(T entity)
        //{
        //    IAllowKeyAssignment keyAssignable = entity as IAllowKeyAssignment;

        //    if (keyAssignable == null)
        //        return;

        //    object key = GetNewKey(entity);

        //    keyAssignable.AssignKey(key);
        //}

        #endregion

        #region Overridable Members

        protected virtual T CloneEntity(T entity)
        {                        
            // Custom clone function takes priority
            if (_cloneFunc != null)
                return _cloneFunc.Invoke(entity);

            // ICloneable interface takes priority
            ICloneable cloneable = entity as ICloneable;

            if (cloneable != null)
                return (T) cloneable.Clone();

            // Try firing CloneEntity event
            if(CloneEntityEvent != null)
            {
                CloneEntityEventArgs eventArgs = new CloneEntityEventArgs(entity);

                CloneEntityEvent.Invoke(this, eventArgs);

                if (eventArgs.Cloned != null)
                    return (T)eventArgs.Cloned;
            }

            // No way to clone, throw exception.
            throw new InvalidOperationException("Unable to clone entity. "
                + "Allow the entity to be cloned by either implements the ICloneable interface on the entity class, "
                + "or handle the CloneEntityEvent of the EntityValueSet<T> instance.");
        }

        //protected virtual object GetNewKey(T entity)
        //{
        //    // Custom new-key function takes priority
        //    if (_getNewKeyFunc != null)
        //        return _getNewKeyFunc.Invoke(entity);

        //    // No way to make new key, return the key of entity instaed
        //    return entity.Key;
        //}

        protected virtual bool KeyEquals(object key1, object key2)
        {
            if (key1 == null && key2 == null)
                throw new InvalidOperationException("At least one of the keys in comparison must not be null");

            if (object.ReferenceEquals(key1, key2))
                return true;

            if (key1 == null)
                return key2.Equals(key1);
            else
               return key1.Equals(key2);            
        }

        #endregion
        
        #region IEntitySet implementation

        public void FindByKey(object key, out IEntity result)
        {
            result = this.FindByKey(key);
        }

        public void GetAll(out IEnumerable<IEntity> result)
        {
            result = this.GetAll().Select(x => (IEntity)x);
        }

        public bool AddAndReplace(IEntity item)
        {
            return this.AddAndReplace((T)item);
        }

        public bool Remove(IEntity item)
        {
            return this.Remove((T)item);
        }

        public void Subtract(IEntitySet another, out IEnumerable<IEntity> result)
        {
            IEnumerable<IEntity> itemsFromAnother;

            another.GetAll(out itemsFromAnother);

            EntitySet<T> typedAnother = new EntitySet<T>(itemsFromAnother.Select(x => (T)x));

            result = this.Subtract(typedAnother)
                .Select(x => (IEntity)x);
        }

        public void Intersect(IEntitySet another, out IEnumerable<IEntity> result)
        {
            IEnumerable<IEntity> itemsFromAnother;

            another.GetAll(out itemsFromAnother);

            EntitySet<T> typedAnother = new EntitySet<T>(itemsFromAnother.Select(x => (T)x));

            result = this.Intersect(typedAnother)
                .Select(x => (IEntity)x);
        }

        #endregion
    
        #region IEntityValueSet implementation

        public void FindByKeyV(object key, out IEntity result)
        {
            result = this.FindByKey(key);
        }

        public void GetAllV(out IEnumerable<IEntity> result)
        {
            result = this.GetAll().Select(x => (IEntity)x);
        }

        public bool AddAndReplaceV(IEntity item)
        {
            return this.AddAndReplace((T) item);
        }

        public void SubtractV(IEntitySet another, out IEnumerable<IEntity> result)
        {
            this.Subtract(another, out result);
        }

        public void IntersectV(IEntitySet another, out IEnumerable<IEntity> result)
        {
            this.Intersect(another, out result);
        }

        #endregion        
    
        #region IEntityValueSet<T> implementation
        
        public T FindByKeyV(object key)
        {
            return this.FindByKey(key);
        }

        public IEnumerable<T> GetAllV()
        {
            return this.GetAll();
        }

        public bool AddAndReplaceV(T item)
        {
            return this.AddAndReplace(item);
        }

        public IEnumerable<T> SubtractV(IEntitySet<T> another)
        {
            return this.Subtract(another);
        }

        public IEnumerable<T> IntersectV(IEntitySet<T> another)
        {
            return this.Intersect(another);
        }

        #endregion
    }
}
